第二章 整个流程

剧透警告!

本章介绍了一个小型示例程序包的开发。这是为了在我们深入研究 R 包的关键组件之前,描绘开发的大图景并提出对一个工作流程的建议。

为了保持快节奏,我们利用了 devtools 程序包和 RStudio IDE 中的现代化的便利功能在之后的章节中,我们将更详细地介绍它们能帮助我们做些什么。

2.1 加载 devtools 和 相关程序包

无论您是否进入了 R 项目,您都可以从任何开放的 R 控制台(R Console)完成以下步骤。

加载 devtools 程序包,在支持程序包开发的各个方面的程序包中,它是个中翘楚。

library(devtools)
#> Loading required package: usethis

仅仅出于演示目的,我们使用 fs 帮助我们在文件系统上工作,使用 tidyverse 进行轻量数据的整理。

library(tidyverse)
library(fs)

2.2 示例程序包:foofactors

我们使用 devtools 中的各种功能从头开始构建一个小型的、作为示例的程序包,它具有在已发布的包中十分常见的功能:存疑

  • 满足特定需求的函数,例如与因子( factors )打交道的“助手”。存疑
  • 可以访问已完成的工作流程以进行安装、获得帮助以及检查包的基本质量。优化表达
  • 版本控制和开放的开发过程。 - 在您的工作中,这是完全可选的,但我们建议这样做。您将看到 Git 和 GitHub 如何帮助我们展示程序包开发的所有中间阶段。
  • 通过 roxygen2 建立各个函数的文档 。
  • 使用 testthat 进行单元测试。
  • 通过可执行文件整体了解软件包的文件 README.Rmd

我们称这个程序包包为 foofactors,它将有几个处理因子( factors )的函数。请注意,这些功能非常简单,并且绝对不是本章的重点!有关正确处理因子( factors )的软件包,请参阅 forcats

构建 foofactors 包本身不是我们的目标。它只是一个示例,用于演示使用 devtools 进行程序包开发的典型工作流程。

2.3 看看成品

我们使用了 Git 版本控制系统追踪 foofactors 包的开发过程。这存粹是可选的,您当然可以不执行这个步骤。但是它有一个附加的好处:我们将其连接到 GitHub 上的远程储存库,这意味着您能够通过访问 GitHub 上的 foofactors 库来观看我们努力取得的光荣成果:https://github.com/jennybc/foofactors。通过检查 commit history(尤其是版本差异),您能够清楚地看到其下列出的过程中每个步骤更改了什么。

TODO: I think these diffs are extremely useful and would like to surface them better here.

2.4 create_package()

调用 create_package(),选择计算机一个目录,初始化新的程序包(如果有需要,还可以新建一个目录)。关于更多的信息,请参见 5.1.3 节。

仔细选择创建新程序包的目录。它可以与其他 R 项目共同位于主目录下。但是,它不应该被嵌套在另一个 RStudio 项目、R 包或者是 Git 存储库中。它也不应该在 R 的程序包库中,因为这个库目录下包含有已经被构建和安装的程序包。将这里创建的源程序包转换为已安装程序包的过程,是 devtools 包可以促进完成的一部分工作。不要尝试为此做 devtools 的工作!存疑关于更多的信息,请参见 5.1.4 节。

将您选择的路径作为参数传入 create_package() 中,如下所示:

create_package("~/path/to/foofactors")

我们必须在临时目录中工作,因为这本书是在云上以非交互形式构建的。在幕后,我们执行自己的 create_package() 命令,但如果我们的输出与你的输出略有不同,请不要感到惊讶。

#> ✔ Creating '/tmp/RtmpsJ87ir/foofactors/'
#> ✔ Setting active project to '/tmp/RtmpsJ87ir/foofactors'
#> ✔ Creating 'R/'
#> ✔ Writing 'DESCRIPTION'
#> Package: foofactors
#> Title: What the Package Does (One Line, Title Case)
#> Version: 0.0.0.9000
#> Authors@R (parsed):
#>     * First Last <first.last@example.com> [aut, cre] (<https://orcid.org/YOUR-ORCID-ID>)
#> Description: What the package does (one paragraph).
#> License: What license it uses
#> Encoding: UTF-8
#> LazyData: true
#> ✔ Writing 'NAMESPACE'
#> ✔ Writing 'foofactors.Rproj'
#> ✔ Adding '.Rproj.user' to '.gitignore'
#> ✔ Adding '^foofactors\\.Rproj$', '^\\.Rproj\\.user$' to '.Rbuildignore'
#> ✔ Setting active project to '<no active project>'
#> ✔ Setting active project to '/tmp/RtmpsJ87ir/foofactors'

如果您在 RStudio 中工作,您会发现自己进入了一个新的 RStudio 程序界面,它已经打开了新的 foofactors 包(或项目)目录。如果您处于某种原因需要手动执行这个操作,请进入该目录并双击 foofactors.Rproj。RStudio 对于软件包做了特殊处理,您现在应该可以在 EvironmentHistory所在的窗格中看到 Build选项卡。

TODO: good place for a screenshot.

在这个新目录里的内容是一个 R 包,也许还是个 RStudio Project?以下是文件清单(在本地,您可以查看 Files 选项卡):

#> # A tibble: 6 x 2
#>   path             type
#>   <fs::path>       <fct>
#> 1 .Rbuildignore    file
#> 2 .gitignore       file
#> 3 DESCRIPTION      file
#> 4 NAMESPACE        file
#> 5 R                directory
#> 6 foofactors.Rproj file

Logo

在文件浏览器中,转到 More > Show Hidden Files 来切换隐藏文件(也称为 dotfiles)的可见性。一些文件是始终可见的,但有时您可能会希望看到全部的文件。

  • .Rbuildignore 列出了我们编写 R 包时需要的,但是从源代码构建 R 包时并不应该包含进来的文件。详情请见 ??
  • .Rproj.user,如果有的话,它是 RStudio 内部使用的目录。
  • .gitignore 为 Git 的使用做好准备。它将忽略一些由 R 或 RStudio 创建的标准的幕后文件。即使您不打算使用 Git,它也是没有妨害的。
  • DESCRIPTION 提供了有关您的程序包的元数据。我们很快将开始编写它。
  • NAMESPACE 声明了程序包导出以供外部使用的函数以及程序包从其他包导入的外部函数。现在,除了一个注释声明这是一个我们不会手工编辑的文件外,它是空的。
  • R/ 目录是程序包的“业务端”。它很快将包含带有函数声明的 .R 文件。
  • foofactors.Rproj 是使得该目录成为 RStudio 项目的文件。即使你不使用 RStudio,这个文件也是没有妨害的。如果您不想创建它,可以使用 create_package(..., rstudio = FALSE)。详情请见 5.2

2.5 use_git()

bulb

Git 或其他版本控制系统的使用是可选项,但是长期来看我们建议您使用。我们将在 16 中解释其重要性。

foofactors 目录是 R 源码包和 RStudio 项目。现在,我们使用 use_git() 让它也变成一个 Git 存储库。

use_git()
#> ✔ Initialising Git repo
#> ✔ Adding '.Rhistory', '.RData' to '.gitignore'

在交互式会话中,系统将询问您是否要在此处提交这些文件,您应该接受这个提议。在 R 中,您不会看到这些,但是在幕后,我们会进行同样的操作。

有什么新内容吗?仅仅是创建了 .git 目录,该目录在大多数环境中都是隐藏的,包括 RStudio 文件浏览器。但是它的存在证明我们确实在这里初始化了 Git 存储库。

dir_info(all = TRUE, regexp = "^[.]git$") %>%
    select(path, type)
#> # A tibble: 1 x 2
#>   path       type
#>   <fs::path> <fct>
#> 1 .git       directory

如果您使用的是 RStudio,它可能会请求在此项目中重新启动。您可以通过退出然后双击 foofactors.Rproj 重新启动来手动执行此操作。现在,除了程序包的开发支持外,您还可以在 Git 选项卡中访问一个基础的 Git 客户端。Git 选项卡与 Environment/History/Build 在同一个窗格中。

TODO: good place for a screenshot.

单击 History (时钟图标),如果您愿意,您将看到通过 use_git() 进行的初始提交:

#> # A tibble: 1 x 3
#>   commit                            author                       message
#>   <chr>                             <chr>                        <chr>
#> 1 4bdca5b6c90d21b1a25e5ef73fd0a5fa… Whole Game <whole_game@exam… "Initial commi…

Logo

只要您设置了 RStudio + Git 集成,RStudio 可以在任何项目中初始化 Git 存储库,即使它不是 R 包项目。依次点击 Tools > Version Control > Project Setup,然后选择 Version control system: Git,然后选择 initialize a new git repository for this project

2.6 编写第一个函数

因子( factors )的很多操作都令人费解。让我们看看当我们把两个因子( factors )连接起来的时候会发生什么。

(a <- factor(c("character", "hits", "your", "eyeballs")))
#> [1] character hits      your      eyeballs
#> Levels: character eyeballs hits your
(b <- factor(c("but", "integer", "where it", "counts")))
#> [1] but      integer  where it counts
#> Levels: but counts integer where it
c(a, b)
#> [1] 1 3 4 2 1 3 4 2

嗯?许多人并没有预料到,将两个因子(factors)连接起来的结果是一个包含数字 1,2,3,4 的整数向量。如果我们将每个因子( factors )强制转换为字符,进行分类,然后重新转换为因子( factors )呢?

factor(c(as.character(a), as.character(b)))
#> [1] character hits      your      eyeballs  but       integer   where it
#> [8] counts
#> Levels: but character counts eyeballs hits integer where it your

这似乎产生了更有意义的结果。让我们将这个操作放在名为 fbind() 的函数体中:

fbind <- function(a, b) {
    factor(c(as.character(a), as.character(b)))
}

这本书不会教您如何在 R 中编写函数。要了解更多有关该知识的信息,请查阅 R for Data ScienceAdvanced RFunctions Chapter

2.7 use_r()

我们该在哪里定义 fbind() 函数?应该将它保存在程序包的 /R 子目录中的 .R 文件内。一个合理的开始是为程序包中的每个函数创建一个新的 .R 文件,并以功能的名字为它命名。当您添加更多的函数后,您可能会希望放宽这个要求,把相关的函数放在一起。我们将 fbind() 的函数定义放在 R/fbind.R 中。

函数 use_r() 将帮助我们在 R/ 目录下创建和(或)打开脚本文件。当您在 .R 文件和关联测试的文件之间互相切换时,它确实在成熟的程序包中十分有用。但是,即使在这里,在 Untitled4 中工作时,它也可以避免您因为太投入而忘记了当前工作的目录。 存疑

use_r("fbind")
#> ● Edit 'R/fbind.R'
#> ● Call `use_test()` to create a matching test file

将函数 fbind() 的定义并且仅仅是 fbind() 的定义放在 R/fbind.R 中并保存。文件 R/fbind.R 不应包含我们最近执行的其他任何顶级代码,例如因子 ablibrary(devtools)use_git() 的定义。这预示了在从编写 R 脚本过渡到 R 包时需要进行的调整。程序包和脚本使用不同的机制来声明它们对其他包的依赖,不同之处还有存储示例或测试代码的方式。我们在第 6 章中进一步探讨这一点。

2.8 load_all()

我们应该怎样来测试 fbind() 函数?如果这是常规的 R 脚本,则可以使用 RStudio 将函数定义发送到 R 控制台,并且在全局工作空间中定义 fbind()。或者,我们可以使用 source("R/fbind.R") 来调用该脚本。但是,在程序包开发中,devtools 提供了更为可靠的方法。有关更多信息,请参见 5.4 节。

调用 load_all() 使得函数 fbind() 可用于测试。

load_all()
#> Loading foofactors

现在,调用 fbind(a, b) 看看它是怎样工作的。

fbind(a, b)
#> [1] character hits      your      eyeballs  but       integer   where it
#> [8] counts
#> Levels: but character counts eyeballs hits integer where it your

我们可以注意到,虽然 fbind() 函数并不在全局工作空间中,但是 load_all() 使得我们可以使用该函数。

exists("fbind", where = globalenv(), inherits = FALSE)
#> [1] FALSE

load_all() 模拟了构建、安装和添加 foofactors 程序包的过程。当您的程序包积累了更多的函数时,有的导出了而有的没有,有的互相调用而有的从依赖的其他程序包中调用,load_all() 相比于在全局工作空间中测试 drive何解?函数定义,能够使您对于程序包的开发方式有更为准确的了解。同样的,load_all() 允许比实际中构建、安装和添加程序包快得多的迭代。

到目前为止的内容:

  • 我们已经编写了第一个函数 fbind(),它能够将两个因子( factors )连接起来。

  • 我们使用 load_all() 快捷地使得该函数可以用于交互使用,就好像我们已经构建并安装了 foofactors,并通过 library(foofactors) 添加到了工作环境中一样。

    Logo

    RStudio 提供了 load_all() 的快速调用。它位于 Build 菜单和 Build 窗格中,通过 More > Load All 或者键盘快捷键 Ctrl + Shift + L (WIndows & Linux) 或者 Cmd + Shift + L (MacOS)调用。

2.8.1 提交 fbind() 的更改

如果您使用了 Git,可以使用您喜欢的方法来提交新的 R/fbind.R 文件。我们在幕后也是这样做的。以下是提交前后相关的差异。

#> diff --git a/R/fbind.R b/R/fbind.R
#> new file mode 100644
#> index 0000000..7b03d75
#> --- /dev/null
#> +++ b/R/fbind.R
#> @@ -0,0 +1,3 @@
#> +fbind <- function(a, b) {
#> +  factor(c(as.character(a), as.character(b)))
#> +}

从这一小节之后,我们每一步之后都会进行提交。这些提交在公共储存库中都是可用的。

2.9 check()

我们有经验可以证明 fbind() 是有效的。但是,我们如何确保 foofactors 包的其他所有可用的功能 moving parts如何翻译 仍然有效呢?在添加很少的代码之后,仍然进行检查似乎很愚蠢,但养成经常检查的习惯是很好的。

R CMD check 会在 shell 中执行,它是检查 R 包是否处于完整工作状态的黄金标准。在不离开 R 会话的情况下,check() 是运行这个命令的便捷方法。

请注意,check() 将生成相当多的输出,并针对交互式使用进行了优化。我们在这里截取了一部分,并仅仅展示摘要。本地运行 check() 的输出将有所不同。

check()
#> ── R CMD check results ────────────────────────────── foofactors 0.0.0.9000 ────
#> Duration: 9.1s
#>
#> ❯ checking DESCRIPTION meta-information ... WARNING
#>   Non-standard license specification:
#>     `use_mit_license()`, `use_gpl3_license()` or friends to pick a
#>     license
#>   Standardizable: FALSE
#>
#> 0 errors ✔ | 1 warning ✖ | 0 notes ✔

请阅读 check 的输出内容!尽可能早并经常性的解决出现的问题。就像在 .R.Rmd 文件上的增量开发一样,您进行全面检查以确保一切正常的间隔时间越长,查明并解决问题的难度也越大。

在这一步中,我们收到了 1 个警告(0 个错误,0 个注释):

  • Non-standard license specification

我们将尽快解决它们。

Logo

RStudio 提供了 check() 的快速调用。它位于 Build 菜单和 Build 窗格中,通过 Check 或者键盘快捷键 Ctrl + Shift + E (WIndows & Linux) 或者 Cmd + Shift + E (MacOS)调用。

2.10 编辑 DESCRIPTION

在解决有关许可证的警告之前,让我们先处理 DESCRIPTION 中的模板内容。DESCRIPTION 文件提供了有关您的程序包的元数据,我们将在第 7 章中全面介绍它。

在该文件中进行如下编辑; - 在 Author 字段中填上您的名字。 - 在 Title 和 Description 字段中填上一些描述性的内容。

Logo

在 RStudio 中可以使用 Ctrl + . 并键入 DESCRIPTION 来激活一个帮助程序,这样您可以轻松地打开该文件并编辑。除了可以键入文件名外,还可以是函数名。一旦程序包在 R/ 目录下有许多函数文件时,这将十分方便。

当您完成后,DESCRIPTION 文件应该看起来像这样:

Package: foofactors
Title: Make Factors Less Aggravating
Version: 0.0.0.9000
Authors@R:
    person("Jane", "Doe", email = "jane@example.com", role = c("aut", "cre"))
Description: Factors have driven people to extreme measures, like ordering
    custom conference ribbons and laptop stickers to express how HELLNO we
    feel about stringsAsFactors. And yet, sometimes you need them. Can they
    be made less maddening? Let's find out.
License: What license it uses
Encoding: UTF-8
LazyData: true

2.11 use_mit_license()

对于 foofactors,我们将使用 MIT 许可证。这需要在 DESCRIPTION 文件中进行规范的描述,并要求一个叫做 LICENSE 的附加文件来声明版权所有者和年份。我们将使用帮助程序 use_mit_license(),参数替换成您的名字。

use_mit_license("Jane Doe")
#> ✔ Setting License field in DESCRIPTION to 'MIT + file LICENSE'
#> ✔ Writing 'LICENSE.md'
#> ✔ Adding '^LICENSE\\.md$' to '.Rbuildignore'
#> ✔ Writing 'LICENSE'

打开新创建的 LICENSE 文件,确保它具有正确的年份和您的名字。

YEAR: 2019
COPYRIGHT HOLDER: Jane Doe

和其他创建许可证的函数一样,use_mit_license() 还将完整的许可证副本放入 LICENSE.md 文件,并将该文件添加进 .Rbuildignore 中。最好的做法是使程序包的源代码中包含完整的许可证,就像在 GitHub 中一样,但是 CRAN 禁止在程序包源代码中包含此文件。

2.12 document()

就像其他 R 函数那样,在使用 fbind() 时能够获得帮助文档,这不是很好吗?这要求程序包具有特殊的 R 文档文件,man/fbind.Rd,一个以类似于 LaTeX的 R 的特殊标记语言编写的文档。幸运的是,我们不一定需要直接编辑它。

我们在源代码文件中的 fbind() 函数体上直接编写一个特别格式的注释,然后让一个名为 roxygen2 的包来完成 man/fbind.Rd 的创建。roxygen2 的使用和机制将在第 8 章中介绍。

如果您使用 RStudio,则在源代码编辑器中打开 R/fbind.R,并将光标放在 fbind() 函数定义中的某处。现在点击 Code > Insert roxygen skeleton。函数上方应该会出现一个非常特殊的注释模板,每行以 #' 开头。RStudio 只插入模板框架,因此您需要对其进行编辑,如下所示。

如果您不使用 RStudio,请自己创建注释。无论如何,您应该修改它,让它看起来像下面那样:

#' Bind two factors
#'
#' Create a new factor from two existing factors, where the new factor's levels
#' are the union of the levels of the input factors.
#'
#' @param a factor
#' @param b factor
#'
#' @return factor
#' @export
#' @examples
#' fbind(iris$Species[c(1, 51, 101)], PlantGrowth$group[c(1, 11, 21)])

TODO: mention how RStudio helps you execute examples here?

但是我们还没有完成!我们还需要用 document() 将这个新的 roxygen 注释转变为 man/fbind.Rd

document()
#> Updating foofactors documentation
#> Updating roxygen version in /private/var/folders/24/8k48jl6d249_n_qfxwsl6xvm0000gn/T/RtmpKNHtqz/foofactors/DESCRIPTION
#> Loading foofactors
#> Writing NAMESPACE
#> Writing fbind.Rd

Logo

RStudio 提供了 document() 的快速调用。它位于 Build 菜单和 Build 窗格中,通过 More > Document 或者键盘快捷键 Ctrl + Shift + D (WIndows & Linux) 或者 Cmd + Shift + D (MacOS)调用。

您现在应该已经可以预览帮助文档,如下所示:

?fbind

您将看到一条消息,如 “Rendering development documentation for ‘fbind’”,它提醒您,您基本上是在预览文档草稿。也就是说,该文档存在于程序包的源代码中,但尚未存在于已安装的包中。事实上,我们还没有安装 foofactors,但我们很快就会安装它。

另请注意,在正式构建和安装包之前,您的程序包的文档不会被正确连接。这样可以改善一些细微之处,例如帮助文档之间的链接和程序包索引的创建。 暂时不能理解

2.12.1 NAMESPACE 的更改

除了将 fbind() 函数的特殊注释转变为 man/fbind.Rd 文档,调用 document() 还能基于 roxygen 注释中的 @export 指令更新 NAMESPACE。 您可以检查 NAMESPACE 文件,里面的内容应为:

# Generated by roxygen2: do not edit by hand

export(fbind)

NAMESPACE 中的 export 指令是通过 library(foofactors) 添加 foofactors 库后,fbind() 函数对于用户可用的原因。就像可以“亲手”编写 .Rd 文件一样,您可以自己显式地管理 NAMESPACE 文件。但是我们选择将其委托给 devtools(以及 roxygen2)。

2.13 再次 check()

foofactors 现在应该可以立刻并永远通过 R CMD check 了:0 个错误,0 个警告,0 个注释。

check()
#> ── R CMD check results ────────────────────────────── foofactors 0.0.0.9000 ────
#> Duration: 12.8s
#>
#> 0 errors ✔ | 0 warnings ✔ | 0 notes ✔

2.14 install()

由于我们现在已经有了一个最小的完整可行的产品,因此可以通过 install() 将 foofactors 包安装到您的库中:

install()
   checking for file/private/var/folders/24/8k48jl6d249_n_qfxwsl6xvm0000gn/T/RtmpKNHtqz/foofactors/DESCRIPTION...checking for file/private/var/folders/24/8k48jl6d249_n_qfxwsl6xvm0000gn/T/RtmpKNHtqz/foofactors/DESCRIPTION’
─  preparingfoofactors:
checking DESCRIPTION meta-information ...checking DESCRIPTION meta-informationchecking for LF line-endings in source and make files and shell scriptschecking for empty or unneeded directoriesbuildingfoofactors_0.0.0.9000.tar.gzRunning /Library/Frameworks/R.framework/Resources/bin/R CMD INSTALL \
/var/folders/24/8k48jl6d249_n_qfxwsl6xvm0000gn/T//RtmpKNHtqz/foofactors_0.0.0.9000.tar.gz \
--install-tests
* installing to library/Users/runner/work/_temp/Library* installing *source* packagefoofactors...
** using staged installation
** R
** byte-compile and prepare package for lazy loading
** help
*** installing help indices
** building package indices
** testing if installed package can be loaded from temporary location
** testing if installed package can be loaded from final location
** testing if installed package keeps a record of temporary installation path
* DONE (foofactors)

Logo

RStudio 在 Build菜单和 Build窗格提供了类似的功能,通过 Install and Restart调用。

现在,我们可以像其他任何程序包一样添加并使用 foofactors 了。让我们从头回顾一下我们的小例子。这是重新启动 R 会话(R Session)并清理工作区的好时机。

library(foofactors)

a <- factor(c("character", "hits", "your", "eyeballs"))
b <- factor(c("but", "integer", "where it", "counts"))

fbind(a, b)
#> [1] character hits      your      eyeballs  but       integer   where it
#> [8] counts
#> Levels: but character counts eyeballs hits integer where it your

我们成功了!

2.15 use_testthat()

在一个示例中,我们已经对 fbind() 进行了简单测试,我们可以通过一些单元测试(unit test)来形式化和扩展它。这意味着我们对于fbind() 在各种输入数据下的正确结果得有一些具体的期望。

首先,我们声明我们将使用 testthat 包中的 use_testthat() 来编写单元测试:

use_testthat()
#> ✔ Adding 'testthat' to Suggests field in DESCRIPTION
#> ✔ Creating 'tests/testthat/'
#> ✔ Writing 'tests/testthat.R'
#> ● Call `use_test()` to initialize a basic test file and open it for editing.

这将为您的程序包初始化单元测试机器。它在 DESCRIPTION 中添加了 Suggests: testthat ,创建了目录 test/testthat 斌添加了脚本 test/testthat.R

然而,实际的测试仍然是由来编写!

函数 use_test() 打开并/或创建测试文件。您可以提供文件名,或者,如果您在RStudio中编辑相关的源文件,文件名将自动生成。由于本书是非交互构建的,我们必须显式地提供文件名:

use_test("fbind")
#> ✔ Increasing 'testthat' version to '>= 2.1.0' in DESCRIPTION
#> ✔ Writing 'tests/testthat/test-fbind.R'

它将会生成文件 tests/testthat/test-fbind.R 。将以下内容编写入该文件:

test_that("fbind() binds factor (or character)", {
    x <- c("a", "b")
    x_fact <- factor(x)
    y <- c("c", "d")
    z <- factor(c("a", "b", "c", "d"))

    expect_identical(fbind(x, y), z)
    expect_identical(fbind(x_fact, y), z)
})

这将测试 fbind() 在组合两个因子(factors)、一个字符向量(character vector)和一个因子(factor)时是否会给出预期结果。

以交互方式运行此测试,就像编写自己的测试一样。注意,您必须首先在 R 会话(R Session)中通过 lirary(testthat) 添加 testthat,并且您可能需要 load(all)

接下来,您的测试将主要(en masse)通过 test() 集中(arms’s length)运行:

TODO: work on the aesthetics of this output. Or maybe testthat 3e will save me the trouble.

test()
#> ✔ |  OK F W S | Context
#>|   0       | fbind|   2       | fbind
#>
#> ══ Results ═════════════════════════════════════════════════════════════════════
#> [ PASS x2 FAIL x0 WARN x0 SKIP x0 ]

Logo

RStudio 提供了 test() 的快速调用。它位于在 Build菜单和 Build窗格中,通过 More > Test package, 或者键盘快捷键 Ctrl + Shift + T (WIndows & Linux) 或者 Cmd + Shift + T (MacOS)调用。

每当您使用 check() 检查程序包时,您的测试也会运行。这样,您基本上就可以使用自己的一些特定于您的包的检查代码来增强标准检查。使用 cove package 跟踪该测试所执行的源代码的比例也是一个好主意。更多细节见第 10 章。

2.16 use_package()

您将不可避免地在自己的程序包中使用其他程序包中的函数。就像我们需要导出函数 fbind() 一样,我们需要从其他程序包的命名空间内导入函数。如果您打算将您的程序包提交到 CRAN,请注意,这甚至适用于那些您认为“始终可用”的程序包,例如 stats::median() 或者是 utils::head()

我们将向 foofactors 包中添加另一个函数,该函数将会为因子(factors)生成一个排序的频率表。我们将从 forcats 包中借用一些技巧,特别是函数 forcats::fct_count()

首先,使用 use_package() 声明我们将使用 forcats 命名空间中的某些函数:

use_package("forcats")
#> ✔ Adding 'forcats' to Imports field in DESCRIPTION
#> ● Refer to functions with `forcats::fun()`

这会将 forcats 包添加进 DESCRIPTION 中的 “Imports” 部分,仅此而已。

现在,我们向 foofactors 中添加第二个函数:假设我们想要一个因子( factors )的频率表,它的形式是一个具有漂亮变量名的常规数据框(data frame),而不是作为 table 类的一个对象或者是有奇怪名字的一些东西。我们还对它进行排序,使得最受欢迎的层级位于顶部。

使用 use_r()R/` 下初始化一个新的 ``.R 文件:

use_r("fcount")
#> ● Edit 'R/fcount.R'
#> ● Call `use_test()` to create a matching test file

将以下内容放入 R/fcount.R

#' Make a sorted frequency table for a factor
#'
#' @param x factor
#'
#' @return A tibble
#' @export
#' @examples
#' fcount(iris$Species)
fcount <- function(x) {
    forcats::fct_count(x, sort = TRUE)
}

请注意我们是如何使用 forcats:: 作为对 forcats 中函数的调用。这样我们指定了要从 forcats 命名空间内调用 fct_count() 函数。从其他程序包中调用函数的方法不止一种,我们在这里使用的方法在第 11 章中有详细的说明。

通过使用 load_all() 模拟程序包的安装来尝试新的 fcount() 函数。

load_all()
#> Loading foofactors
fcount(iris$Species)
#> # A tibble: 3 x 2
#>   f              n
#>   <fct>      <int>
#> 1 setosa        50
#> 2 versicolor    50
#> 3 virginica     50

通过 document() 来生成相关联的帮助文档。这一过程也会将 fcount() 作为一个导出以供使用的函数添加进 NAMESPACE

document()
#> Updating foofactors documentation
#> Writing NAMESPACE
#> Loading foofactors
#> Writing NAMESPACE
#> Writing fcount.Rd

2.17 use_github()

您已经看到我们在 foofactors 的开发过程中进行了许多提交。您可以在 https://github.com/jennybc/foofacts 上看到指示性的历史记录。我们使用版本控制系统并公开开发过程的决定意味着您可以在每个开发阶段检查 foofactors 源代码的状态。通过查看所谓的 diff,您可以确切地看到每个 devtools 帮助函数是如何修改构成 foofactors 程序包的源文件。

如何将本地 foofactors 程序包和 Git 存储库连接到 GitHub 上的配套存储库呢?

  • use_github() 是我们一直以来推荐的帮助函数。我们不会在这里演示,因为它需要在您的主机端进行一些特殊的设置。我们也不想在每次建立这本书的时候都删除和重建公共 foofactors 程序包。
  • 先设置 GitHub repo!这听起来有悖常理,但让您的工作进入 GitHub 的最简单方法是在那里启动,然后使用 RStudio 在同步的本地副本中开始工作。这种方法在 Happy Git的工作流 New project,GitHub firstExisting project,GitHub first 中描述。
  • 命令行 Git(Command line Git)始终可以用于添加远程存储库 post hoc。这在 Happy Git 的工作流 ` Existing project, GitHub last <https://happygitwithr.com/existing-github-last.html>`__ 中进行了描述。

这些方法中的任何一种都会将本地 foofactors 项目连接到 GitHub repo(公共或私有),您可以使用 RStudio 中内置的 Git 客户端来推送或拉取它。

2.18 use_readme_rmd()

既然您的程序包位于 GitHub 上,那么 README.md 文件就很重要。它是程序包的主页和欢迎界面,至少在你决定为它建立一个网站(见 pkgdown)、添加一个 vignette(见第 9 章)或提交给 CRAN 之前(见第 18 章)。

use_readme_rmd() 函数的作用是初始化一个基本的、可执行的 README.Rmd,以便您编辑:

use_readme_rmd()
#> ✔ Writing 'README.Rmd'
#> ✔ Adding '^README\\.Rmd$' to '.Rbuildignore'
#> ✔ Writing '.git/hooks/pre-commit'

除了创建 README.Rmd 之外,它还会在 .Rbuildignore 中添加一些行,并创建一个 Git 预提交 Hook,以帮助您保持 README.RmdREADME.md 的同步。

README.Rmd 已经有了如下部分:

  • 提示您描述程序包的用途。
  • 提供安装程序包的代码。
  • 提示您展示一些用法。

如何填充这个模板骨架?从 DESCRIPTION 和任何正式或非正式的测试或例子中大量地复制内容。有内容总比什么都没有好。否则……您希望人们自己安装您的程序包,仔细检查各个帮助文件,并找出如何使用它吗?他们可能并不会这样做。

我们喜欢用 R Markdown 来编写 README,因此它可以展示实际的用法。包含实时代码还可以减少 README 变得过时和与实际的程序包不同步的可能性。

如果 RStudio 还没有这样做,请打开 README.Rmd 进行编辑。例如,确保它显示了 fbind() 和/或 fcount() 的一些用法。

我们使用的 README.Rmd 在这里: README.Rmd ,以下是内容:

TODO: update this link after merge into r-pkgs.

---
output: github_document
---

<!-- README.md is generated from README.Rmd. Please edit that file -->

```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/README-",
out.width = "100%"
)
```

**NOTE: This is a toy package created for expository purposes, for the second edition of [R Packages](https://r-pkgs.org). It is not meant to actually be useful. If you want a package for factor handling, please see [forcats](https://forcats.tidyverse.org).**

# foofactors

<!-- badges: start -->
<!-- badges: end -->

Factors are a very useful type of variable in R, but they can also be very aggravating. This package provides some helper functions for the care and feeding of factors.

## Installation

You can install foofactors like so:

``` r
devtools::install_github("jennybc/foofactors")
```

## Quick demo

Binding two factors via `fbind()`:

```{r}
library(foofactors)
a <- factor(c("character", "hits", "your", "eyeballs"))
b <- factor(c("but", "integer", "where it", "counts"))
```

Simply catenating two factors leads to a result that most don't expect.

```{r}
c(a, b)
```

The `fbind()` function glues two factors together and returns factor.

```{r}
fbind(a, b)
```

Often we want a table of frequencies for the levels of a factor. The base `table()` function returns an object of class `table`, which can be inconvenient for downstream work.

```{r}
set.seed(1234)
x <- factor(sample(letters[1:5], size = 100, replace = TRUE))
table(x)
```

The `fcount()` function returns a frequency table as a tibble with a column of factor levels and another of frequencies:

```{r}
fcount(x)
```

别忘了渲染它并产生 README.md!如果您尝试提交 README.Rmd 而不是``README.md``,并且 README.md 似乎已过期,那么预提交 Hook 应该会提醒您。

渲染 README.Rmd 的最好方式是和 build_readme() 一起使用,因为它会注意使用程序包的最新版本进行渲染,即从当前源安装一个临时副本。

build_readme()
#> Installing foofactors in temporary library
#> Building /private/var/folders/24/8k48jl6d249_n_qfxwsl6xvm0000gn/T/RtmpKNHtqz/foofactors/README.Rmd

您只需要访问 GitHub 上的 foofactors 就可以看到已经渲染的 README.md

最后,别忘了做最后一次提交。如果您用的是 GitHub,还需要推送至远程仓库。

2.19 最后一步: check() 以及 install()

让我们再次运行 check() 以确保程序包一切正常。

check()
#> ── R CMD check results ────────────────────────────── foofactors 0.0.0.9000 ────
#> Duration: 13.4s
#>
#> 0 errors ✔ | 0 warnings ✔ | 0 notes ✔

foofactors 应该没有错误、警告或注释信息。现在是重新构建和安装它的最好时机。庆祝一下!

install()
   checking for file/private/var/folders/24/8k48jl6d249_n_qfxwsl6xvm0000gn/T/RtmpKNHtqz/foofactors/DESCRIPTION...checking for file/private/var/folders/24/8k48jl6d249_n_qfxwsl6xvm0000gn/T/RtmpKNHtqz/foofactors/DESCRIPTION’
─  preparingfoofactors:
checking DESCRIPTION meta-information ...checking DESCRIPTION meta-informationchecking for LF line-endings in source and make files and shell scriptschecking for empty or unneeded directories
Removed empty directoryfoofactors/tests/testthat/_snaps’
─  buildingfoofactors_0.0.0.9000.tar.gzRunning /Library/Frameworks/R.framework/Resources/bin/R CMD INSTALL \
/var/folders/24/8k48jl6d249_n_qfxwsl6xvm0000gn/T//RtmpKNHtqz/foofactors_0.0.0.9000.tar.gz \
--install-tests
* installing to library/Users/runner/work/_temp/Library* installing *source* packagefoofactors...
** using staged installation
** R
** tests
** byte-compile and prepare package for lazy loading
** help
*** installing help indices
** building package indices
** testing if installed package can be loaded from temporary location
** testing if installed package can be loaded from final location
** testing if installed package keeps a record of temporary installation path
* DONE (foofactors)

请随意访问 GitHub 上的 foofactors package,它正是在这里开发的。提交历史记录反映了每个单独的步骤,因此可以使用 diff 来查看随着包的发展,哪些文件被添加和修改。本书的其余部分将更详细地介绍您在这里看到的每一个步骤以及更多内容。